/*
* Copyright (C) 2013 Baidu.com Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baidu.cafe.local.record;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import org.json.JSONObject;
import android.graphics.Bitmap;
import android.net.http.SslError;
import android.os.Build;
import android.os.Message;
import android.os.SystemClock;
import android.view.KeyEvent;
import android.view.View;
import android.webkit.ConsoleMessage;
import android.webkit.GeolocationPermissions.Callback;
import android.webkit.HttpAuthHandler;
import android.webkit.JsPromptResult;
import android.webkit.JsResult;
import android.webkit.SslErrorHandler;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebChromeClient.CustomViewCallback;
import android.webkit.WebResourceResponse;
import android.webkit.WebStorage.QuotaUpdater;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.baidu.cafe.CafeTestCase;
import com.baidu.cafe.local.DESEncryption;
import com.baidu.cafe.local.LocalLib;
import com.baidu.cafe.local.Log;
/**
* one object one webview
*
* @author leiming@baidu.com
* @date 2013-4-12
* @version
* @todo
*/
public class WebElementRecorder {
private ViewRecorder viewRecorder = null;
private WebView webView = null;
private WebElementEventCreator webElementEventCreator = null;
private final static int TIMEOUT_NEXT_EVENT = 500;
/**
* lock for OutputEventQueue
*/
private static String mSyncWebElementRecordEventQueue = new String("mSyncWebElementRecordEventQueue");
private Queue<WebElementRecordEvent> mWebElementRecordEventQueue = new LinkedList<WebElementRecordEvent>();
public WebElementRecorder(ViewRecorder viewRecorder) {
this.viewRecorder = viewRecorder;
this.webElementEventCreator = new WebElementEventCreator(viewRecorder);
this.mWebElementRecordEventQueue.clear();
}
public boolean offerWebElementRecordEventQueue(WebElementRecordEvent event) {
synchronized (mSyncWebElementRecordEventQueue) {
return mWebElementRecordEventQueue.offer(event);
}
}
public WebElementRecordEvent peekWebElementRecordEventQueue() {
synchronized (mSyncWebElementRecordEventQueue) {
return mWebElementRecordEventQueue.peek();
}
}
public WebElementRecordEvent pollWebElementRecordEventQueue() {
synchronized (mSyncWebElementRecordEventQueue) {
return mWebElementRecordEventQueue.poll();
}
}
private void sleep(long time) {
try {
Thread.sleep(time);
} catch (Exception ignored) {
}
}
/**
* set hook listener on WebView body
*
* @param webView
* @return
*/
public void handleWebView(final WebView webView) {
if (webView == null) {
return;
}
print("start monitor WebView: " + webView);
webView.post(new Runnable() {
public void run() {
print("webView getURL: " + webView.getUrl());
}
});
this.webView = webView;
// monitor WebView
hookWebView(webView);
handleWebElementRecordEventQueue();
}
public void handleWebElementRecordEventQueue() {
new Thread(new Runnable() {
public void run() {
ArrayList<WebElementRecordEvent> events = new ArrayList<WebElementRecordEvent>();
while (true) {
WebElementRecordEvent lastRecordEvent = null;
long endTime = System.currentTimeMillis() + TIMEOUT_NEXT_EVENT;
WebElementRecordEvent e = null;
while (true) {
e = null;
if ((e = pollWebElementRecordEventQueue()) != null) {
// here comes a record event
endTime = System.currentTimeMillis() + TIMEOUT_NEXT_EVENT;
if (lastRecordEvent == null) {
// if e is the first record event
events.add(e);
lastRecordEvent = e;
} else {
if (e.time > lastRecordEvent.time
&& e.familyString.equals(lastRecordEvent.familyString)) {
events.add(e);
lastRecordEvent = e;
} else {
offerOutputEventQueue(events);
lastRecordEvent = null;
events.clear();
}
}
} else {
// wait until timeout, then offerOutputEventQueue
if (System.currentTimeMillis() > endTime) {
offerOutputEventQueue(events);
lastRecordEvent = null;
events.clear();
sleep(50);
break;
}
}
sleep(10);
}
}
}
}).start();
}
private boolean offerOutputEventQueue(ArrayList<WebElementRecordEvent> events) {
if (events.isEmpty()) {
return false;
}
boolean eventOffered = false;
boolean isTouchstart = false;
boolean isTouchmove = false;
for (WebElementRecordEvent e : events) {
if ("touchstart".equals(e.action)) {
isTouchstart = true;
} else if ("touchmove".equals(e.action)) {
if (isTouchstart) {
isTouchmove = true;
} else {// touchmove without touchstart before, note as illegal
eventOffered = false;
break;
}
} else if ("touchcancel".equals(e.action)) {
eventOffered = false;
break;
} else if ("touchend".equals(e.action)) {
// when it comes touchstart -> touchend without touchmove in the
// list, offer a click action
if (isTouchstart && !isTouchmove) {
eventOffered = true;
// break;
} else {
// touchstart -> touchmove -> touchend
eventOffered = false;
// break;
}
break;
} else if ("click".equals(e.action)) {
eventOffered = true;
break;
}
}
if (eventOffered) {
WebElementRecordEvent e = events.get(0);
OutputEvent outputEvent = new WebElementClickEvent(e.view);
outputEvent.setCode(String.format(
"local.recordReplay.clickOnWebElementByFamilyString(\"%s\"); // Click On [%s]", e.familyString,
e.tag));
viewRecorder.offerOutputEventQueue(outputEvent);
}
return eventOffered;
}
/**
* @param webView
* @return
*/
private WebViewClient getOriginalWebViewClient(final WebView webView) {
// save old WebViewClient
WebViewClient mWebViewClient = null;
try {
// print("Build.VERSION.SDK_INT : " + Build.VERSION.SDK_INT);
if (Build.VERSION.SDK_INT > 14) {
Object originalWebViewClassic = viewRecorder.getLocalLib().invoke(webView, "android.webkit.WebView",
"getWebViewProvider", new Class[] {}, new Object[] {});
Object originalCallbackProxy = viewRecorder.getLocalLib().getField(originalWebViewClassic, null,
"mCallbackProxy");
mWebViewClient = (WebViewClient) viewRecorder.getLocalLib().getField(originalCallbackProxy, null,
"mWebViewClient");
} else {
print("getClass:" + webView.getClass());
mWebViewClient = (WebViewClient) viewRecorder.getLocalLib().invoke(webView, "android.webkit.WebView",
"getWebViewClient", new Class[] {}, new Object[] {});
}
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return mWebViewClient;
}
/**
* use java reflect mechanism to get original WebChromeClient object
*
* @param webView
* @return
*/
private WebChromeClient getOriginalWebChromeClient(final WebView webView) {
WebChromeClient mWebChromeClient = null;
try {
if (Build.VERSION.SDK_INT > 14) {
Object originalWebViewClassic = viewRecorder.getLocalLib().invoke(webView, "android.webkit.WebView",
"getWebViewProvider", new Class[] {}, new Object[] {});
Object originalCallbackProxy = viewRecorder.getLocalLib().getField(originalWebViewClassic, null,
"mCallbackProxy");
mWebChromeClient = (WebChromeClient) viewRecorder.getLocalLib().getField(originalCallbackProxy, null,
"mWebChromeClient");
} else {
mWebChromeClient = (WebChromeClient) viewRecorder.getLocalLib().invoke(webView,
"android.webkit.WebView", "getWebChromeClient", new Class[] {}, new Object[] {});
}
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
return mWebChromeClient;
}
public void setHookedWebChromeClient(final WebView webView) {
webElementEventCreator.prepareForStart();
if (webView != null) {
webView.post(new Runnable() {
public void run() {
webView.getSettings().setJavaScriptEnabled(true);
final WebChromeClient originalWebChromeClient = getOriginalWebChromeClient(webView);
if (originalWebChromeClient != null) {
webView.setWebChromeClient(new WebChromeClient() {
HashMap<String, Boolean> invoke = new HashMap<String, Boolean>();
/**
* Overrides onJsPrompt in order to create
* {@code WebElement} objects based on the web
* elements attributes prompted by the injections of
* JavaScript
*/
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue,
JsPromptResult r) {
if (message != null) {
if (message.endsWith("WebElementRecorder-finished")) {
// Log.i("onJsPrompt : " + message);
webElementEventCreator.setFinished(true);
} else {
webElementEventCreator.createWebElementEvent(message, view);
}
}
r.confirm();
return true;
}
@Override
public Bitmap getDefaultVideoPoster() {
Bitmap ret = null;
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
ret = originalWebChromeClient.getDefaultVideoPoster();
invoke.put(funcName, false);
}
return ret;
}
@Override
public View getVideoLoadingProgressView() {
View ret = null;
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
ret = originalWebChromeClient.getVideoLoadingProgressView();
invoke.put(funcName, false);
}
return ret;
}
@Override
public void getVisitedHistory(ValueCallback<String[]> callback) {
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
originalWebChromeClient.getVisitedHistory(callback);
invoke.put(funcName, false);
}
}
@Override
public void onCloseWindow(WebView window) {
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
originalWebChromeClient.onCloseWindow(window);
invoke.put(funcName, false);
}
}
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
boolean ret = false;
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
ret = originalWebChromeClient.onConsoleMessage(consoleMessage);
invoke.put(funcName, false);
}
return ret;
}
@Override
public void onConsoleMessage(String message, int lineNumber, String sourceID) {
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
originalWebChromeClient.onConsoleMessage(message, lineNumber, sourceID);
invoke.put(funcName, false);
}
}
@Override
public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture,
Message resultMsg) {
boolean ret = false;
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
ret = originalWebChromeClient.onCreateWindow(view, isDialog, isUserGesture,
resultMsg);
invoke.put(funcName, false);
}
return ret;
}
@Override
public void onExceededDatabaseQuota(String url, String databaseIdentifier, long quota,
long estimatedDatabaseSize, long totalQuota, QuotaUpdater quotaUpdater) {
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
originalWebChromeClient.onExceededDatabaseQuota(url, databaseIdentifier, quota,
estimatedDatabaseSize, totalQuota, quotaUpdater);
invoke.put(funcName, false);
}
}
@Override
public void onGeolocationPermissionsHidePrompt() {
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
originalWebChromeClient.onGeolocationPermissionsHidePrompt();
invoke.put(funcName, false);
}
}
@Override
public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
originalWebChromeClient.onGeolocationPermissionsShowPrompt(origin, callback);
invoke.put(funcName, false);
}
}
@Override
public void onHideCustomView() {
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
originalWebChromeClient.onHideCustomView();
invoke.put(funcName, false);
}
}
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
boolean ret = false;
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
ret = originalWebChromeClient.onJsAlert(view, url, message, result);
invoke.put(funcName, false);
}
return ret;
}
@Override
public boolean onJsBeforeUnload(WebView view, String url, String message, JsResult result) {
boolean ret = false;
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
ret = originalWebChromeClient.onJsBeforeUnload(view, url, message, result);
invoke.put(funcName, false);
}
return ret;
}
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
boolean ret = false;
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
ret = originalWebChromeClient.onJsConfirm(view, url, message, result);
invoke.put(funcName, false);
}
return ret;
}
@Override
public boolean onJsTimeout() {
boolean ret = false;
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
ret = originalWebChromeClient.onJsTimeout();
invoke.put(funcName, false);
}
return ret;
}
@Override
public void onProgressChanged(WebView view, int newProgress) {
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
originalWebChromeClient.onProgressChanged(view, newProgress);
invoke.put(funcName, false);
}
}
@Override
public void onReachedMaxAppCacheSize(long requiredStorage, long quota,
QuotaUpdater quotaUpdater) {
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
originalWebChromeClient.onReachedMaxAppCacheSize(requiredStorage, quota,
quotaUpdater);
invoke.put(funcName, false);
}
}
@Override
public void onReceivedIcon(WebView view, Bitmap icon) {
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
originalWebChromeClient.onReceivedIcon(view, icon);
invoke.put(funcName, false);
}
}
@Override
public void onReceivedTitle(WebView view, String title) {
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
originalWebChromeClient.onReceivedTitle(view, title);
invoke.put(funcName, false);
}
}
@Override
public void onReceivedTouchIconUrl(WebView view, String url, boolean precomposed) {
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
originalWebChromeClient.onReceivedTouchIconUrl(view, url, precomposed);
invoke.put(funcName, false);
}
}
@Override
public void onRequestFocus(WebView view) {
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
originalWebChromeClient.onRequestFocus(view);
invoke.put(funcName, false);
}
}
@Override
public void onShowCustomView(View view, CustomViewCallback callback) {
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
originalWebChromeClient.onShowCustomView(view, callback);
invoke.put(funcName, false);
}
}
public void onShowCustomView(View view, int requestedOrientation,
CustomViewCallback callback) {
if (Build.VERSION.SDK_INT >= 14) {
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
originalWebChromeClient.onShowCustomView(view, requestedOrientation, callback);
invoke.put(funcName, false);
}
}
}
});
} else {
webView.setWebChromeClient(new WebChromeClient() {
/* (non-Javadoc)
* @see android.webkit.WebChromeClient#getDefaultVideoPoster()
*/
@Override
public Bitmap getDefaultVideoPoster() {
// TODO Auto-generated method stub
return super.getDefaultVideoPoster();
}
/* (non-Javadoc)
* @see android.webkit.WebChromeClient#getVideoLoadingProgressView()
*/
@Override
public View getVideoLoadingProgressView() {
// TODO Auto-generated method stub
return super.getVideoLoadingProgressView();
}
/* (non-Javadoc)
* @see android.webkit.WebChromeClient#getVisitedHistory(android.webkit.ValueCallback)
*/
@Override
public void getVisitedHistory(
ValueCallback<String[]> callback) {
// TODO Auto-generated method stub
super.getVisitedHistory(callback);
}
/* (non-Javadoc)
* @see android.webkit.WebChromeClient#onCloseWindow(android.webkit.WebView)
*/
@Override
public void onCloseWindow(WebView window) {
// TODO Auto-generated method stub
super.onCloseWindow(window);
}
/* (non-Javadoc)
* @see android.webkit.WebChromeClient#onConsoleMessage(android.webkit.ConsoleMessage)
*/
@Override
public boolean onConsoleMessage(
ConsoleMessage consoleMessage) {
// TODO Auto-generated method stub
return super.onConsoleMessage(consoleMessage);
}
/* (non-Javadoc)
* @see android.webkit.WebChromeClient#onConsoleMessage(java.lang.String, int, java.lang.String)
*/
@Override
public void onConsoleMessage(String message,
int lineNumber, String sourceID) {
// TODO Auto-generated method stub
super.onConsoleMessage(message, lineNumber, sourceID);
}
/* (non-Javadoc)
* @see android.webkit.WebChromeClient#onCreateWindow(android.webkit.WebView, boolean, boolean, android.os.Message)
*/
@Override
public boolean onCreateWindow(WebView view,
boolean isDialog, boolean isUserGesture,
Message resultMsg) {
// TODO Auto-generated method stub
return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg);
}
/* (non-Javadoc)
* @see android.webkit.WebChromeClient#onExceededDatabaseQuota(java.lang.String, java.lang.String, long, long, long, android.webkit.WebStorage.QuotaUpdater)
*/
@Override
public void onExceededDatabaseQuota(String url,
String databaseIdentifier, long quota,
long estimatedDatabaseSize,
long totalQuota, QuotaUpdater quotaUpdater) {
// TODO Auto-generated method stub
super.onExceededDatabaseQuota(url, databaseIdentifier, quota,
estimatedDatabaseSize, totalQuota, quotaUpdater);
}
/* (non-Javadoc)
* @see android.webkit.WebChromeClient#onGeolocationPermissionsHidePrompt()
*/
@Override
public void onGeolocationPermissionsHidePrompt() {
// TODO Auto-generated method stub
super.onGeolocationPermissionsHidePrompt();
}
/* (non-Javadoc)
* @see android.webkit.WebChromeClient#onGeolocationPermissionsShowPrompt(java.lang.String, android.webkit.GeolocationPermissions.Callback)
*/
@Override
public void onGeolocationPermissionsShowPrompt(
String origin, Callback callback) {
// TODO Auto-generated method stub
super.onGeolocationPermissionsShowPrompt(origin, callback);
}
/* (non-Javadoc)
* @see android.webkit.WebChromeClient#onHideCustomView()
*/
@Override
public void onHideCustomView() {
// TODO Auto-generated method stub
super.onHideCustomView();
}
/* (non-Javadoc)
* @see android.webkit.WebChromeClient#onJsAlert(android.webkit.WebView, java.lang.String, java.lang.String, android.webkit.JsResult)
*/
@Override
public boolean onJsAlert(WebView view, String url,
String message, JsResult result) {
// TODO Auto-generated method stub
return super.onJsAlert(view, url, message, result);
}
/* (non-Javadoc)
* @see android.webkit.WebChromeClient#onJsBeforeUnload(android.webkit.WebView, java.lang.String, java.lang.String, android.webkit.JsResult)
*/
@Override
public boolean onJsBeforeUnload(WebView view,
String url, String message, JsResult result) {
// TODO Auto-generated method stub
return super.onJsBeforeUnload(view, url, message, result);
}
/* (non-Javadoc)
* @see android.webkit.WebChromeClient#onJsConfirm(android.webkit.WebView, java.lang.String, java.lang.String, android.webkit.JsResult)
*/
@Override
public boolean onJsConfirm(WebView view,
String url, String message, JsResult result) {
// TODO Auto-generated method stub
return super.onJsConfirm(view, url, message, result);
}
/* (non-Javadoc)
* @see android.webkit.WebChromeClient#onJsPrompt(android.webkit.WebView, java.lang.String, java.lang.String, java.lang.String, android.webkit.JsPromptResult)
*/
@Override
public boolean onJsPrompt(WebView view, String url,
String message, String defaultValue,
JsPromptResult result) {
if (message != null) {
if (message.endsWith("WebElementRecorder-finished")) {
// Log.i("onJsPrompt : " + message);
webElementEventCreator.setFinished(true);
} else {
webElementEventCreator.createWebElementEvent(message, view);
}
}
result.confirm();
return true;
}
/* (non-Javadoc)
* @see android.webkit.WebChromeClient#onJsTimeout()
*/
@Override
public boolean onJsTimeout() {
// TODO Auto-generated method stub
return super.onJsTimeout();
}
/* (non-Javadoc)
* @see android.webkit.WebChromeClient#onProgressChanged(android.webkit.WebView, int)
*/
@Override
public void onProgressChanged(WebView view,
int newProgress) {
// TODO Auto-generated method stub
super.onProgressChanged(view, newProgress);
}
/* (non-Javadoc)
* @see android.webkit.WebChromeClient#onReachedMaxAppCacheSize(long, long, android.webkit.WebStorage.QuotaUpdater)
*/
@Override
public void onReachedMaxAppCacheSize(
long requiredStorage, long quota,
QuotaUpdater quotaUpdater) {
// TODO Auto-generated method stub
super.onReachedMaxAppCacheSize(requiredStorage, quota, quotaUpdater);
}
/* (non-Javadoc)
* @see android.webkit.WebChromeClient#onReceivedIcon(android.webkit.WebView, android.graphics.Bitmap)
*/
@Override
public void onReceivedIcon(WebView view, Bitmap icon) {
// TODO Auto-generated method stub
super.onReceivedIcon(view, icon);
}
/* (non-Javadoc)
* @see android.webkit.WebChromeClient#onReceivedTitle(android.webkit.WebView, java.lang.String)
*/
@Override
public void onReceivedTitle(WebView view,
String title) {
// TODO Auto-generated method stub
super.onReceivedTitle(view, title);
}
/* (non-Javadoc)
* @see android.webkit.WebChromeClient#onReceivedTouchIconUrl(android.webkit.WebView, java.lang.String, boolean)
*/
@Override
public void onReceivedTouchIconUrl(WebView view,
String url, boolean precomposed) {
// TODO Auto-generated method stub
super.onReceivedTouchIconUrl(view, url, precomposed);
}
/* (non-Javadoc)
* @see android.webkit.WebChromeClient#onRequestFocus(android.webkit.WebView)
*/
@Override
public void onRequestFocus(WebView view) {
// TODO Auto-generated method stub
super.onRequestFocus(view);
}
/* (non-Javadoc)
* @see android.webkit.WebChromeClient#onShowCustomView(android.view.View, android.webkit.WebChromeClient.CustomViewCallback)
*/
@Override
public void onShowCustomView(View view,
CustomViewCallback callback) {
// TODO Auto-generated method stub
super.onShowCustomView(view, callback);
}
/* (non-Javadoc)
* @see android.webkit.WebChromeClient#onShowCustomView(android.view.View, int, android.webkit.WebChromeClient.CustomViewCallback)
*/
@Override
public void onShowCustomView(View view,
int requestedOrientation,
CustomViewCallback callback) {
// TODO Auto-generated method stub
super.onShowCustomView(view, requestedOrientation, callback);
}
});
}
}
});
}
}
public void setHookedWebViewClient(final WebView webView, final String javaScript) {
webView.post(new Runnable() {
// @Override
public void run() {
final WebViewClient orginalWebViewClient = getOriginalWebViewClient(webView);
if (orginalWebViewClient != null) {
webView.setWebViewClient(new WebViewClient() {
HashMap<String, Boolean> invoke = new HashMap<String, Boolean>();
@Override
public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
orginalWebViewClient.doUpdateVisitedHistory(view, url, isReload);
}
@Override
public void onFormResubmission(WebView view, Message dontResend, Message resend) {
orginalWebViewClient.onFormResubmission(view, dontResend, resend);
}
@Override
public void onLoadResource(WebView view, String url) {
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
orginalWebViewClient.onLoadResource(view, url);
invoke.put(funcName, false);
}
}
@Override
public void onPageFinished(WebView view, String url) {
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
orginalWebViewClient.onPageFinished(view, url);
if (url != null) {
hookWebElements(view, javaScript);
}
invoke.put(funcName, false);
}
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
orginalWebViewClient.onPageStarted(view, url, favicon);
invoke.put(funcName, false);
}
}
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
orginalWebViewClient.onReceivedError(view, errorCode, description, failingUrl);
}
@Override
public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host,
String realm) {
orginalWebViewClient.onReceivedHttpAuthRequest(view, handler, host, realm);
}
public void onReceivedLoginRequest(WebView view, String realm, String account, String args) {
// do support onReceivedLoginRequest since the
// version 4.0
if (Build.VERSION.SDK_INT >= 14) {
orginalWebViewClient.onReceivedLoginRequest(view, realm, account, args);
}
}
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
orginalWebViewClient.onReceivedSslError(view, handler, error);
}
@Override
public void onScaleChanged(WebView view, float oldScale, float newScale) {
orginalWebViewClient.onScaleChanged(view, oldScale, newScale);
}
@Override
public void onTooManyRedirects(WebView view, Message cancelMsg, Message continueMsg) {
orginalWebViewClient.onTooManyRedirects(view, cancelMsg, continueMsg);
}
@Override
public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
orginalWebViewClient.onUnhandledKeyEvent(view, event);
}
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
if (Build.VERSION.SDK_INT >= 14) {
return orginalWebViewClient.shouldInterceptRequest(view, url);
} else {
return null;
}
}
@Override
public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
return orginalWebViewClient.shouldOverrideKeyEvent(view, event);
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
boolean ret = false;
String funcName = new Throwable().getStackTrace()[1].getMethodName();
if (invoke.get(funcName) == null || !invoke.get(funcName)) {
invoke.put(funcName, true);
ret = orginalWebViewClient.shouldOverrideUrlLoading(view, url);
invoke.put(funcName, false);
}
return ret;
}
});
} else {
// set hook WebViewClient
webView.setWebViewClient(new WebViewClient() {
/* (non-Javadoc)
* @see android.webkit.WebViewClient#doUpdateVisitedHistory(android.webkit.WebView, java.lang.String, boolean)
*/
@Override
public void doUpdateVisitedHistory(WebView view,
String url, boolean isReload) {
// TODO Auto-generated method stub
super.doUpdateVisitedHistory(view, url, isReload);
}
/* (non-Javadoc)
* @see android.webkit.WebViewClient#onFormResubmission(android.webkit.WebView, android.os.Message, android.os.Message)
*/
@Override
public void onFormResubmission(WebView view,
Message dontResend, Message resend) {
// TODO Auto-generated method stub
super.onFormResubmission(view, dontResend, resend);
}
/* (non-Javadoc)
* @see android.webkit.WebViewClient#onLoadResource(android.webkit.WebView, java.lang.String)
*/
@Override
public void onLoadResource(WebView view, String url) {
// TODO Auto-generated method stub
super.onLoadResource(view, url);
}
/* (non-Javadoc)
* @see android.webkit.WebViewClient#onPageStarted(android.webkit.WebView, java.lang.String, android.graphics.Bitmap)
*/
@Override
public void onPageStarted(WebView view, String url,
Bitmap favicon) {
// TODO Auto-generated method stub
super.onPageStarted(view, url, favicon);
}
/* (non-Javadoc)
* @see android.webkit.WebViewClient#onReceivedError(android.webkit.WebView, int, java.lang.String, java.lang.String)
*/
@Override
public void onReceivedError(WebView view,
int errorCode, String description,
String failingUrl) {
// TODO Auto-generated method stub
super.onReceivedError(view, errorCode, description, failingUrl);
}
/* (non-Javadoc)
* @see android.webkit.WebViewClient#onReceivedHttpAuthRequest(android.webkit.WebView, android.webkit.HttpAuthHandler, java.lang.String, java.lang.String)
*/
@Override
public void onReceivedHttpAuthRequest(WebView view,
HttpAuthHandler handler, String host,
String realm) {
// TODO Auto-generated method stub
super.onReceivedHttpAuthRequest(view, handler, host, realm);
}
/* (non-Javadoc)
* @see android.webkit.WebViewClient#onReceivedLoginRequest(android.webkit.WebView, java.lang.String, java.lang.String, java.lang.String)
*/
@Override
public void onReceivedLoginRequest(WebView view,
String realm, String account, String args) {
// TODO Auto-generated method stub
super.onReceivedLoginRequest(view, realm, account, args);
}
/* (non-Javadoc)
* @see android.webkit.WebViewClient#onReceivedSslError(android.webkit.WebView, android.webkit.SslErrorHandler, android.net.http.SslError)
*/
@Override
public void onReceivedSslError(WebView view,
SslErrorHandler handler, SslError error) {
// TODO Auto-generated method stub
super.onReceivedSslError(view, handler, error);
}
/* (non-Javadoc)
* @see android.webkit.WebViewClient#onScaleChanged(android.webkit.WebView, float, float)
*/
@Override
public void onScaleChanged(WebView view,
float oldScale, float newScale) {
// TODO Auto-generated method stub
super.onScaleChanged(view, oldScale, newScale);
}
/* (non-Javadoc)
* @see android.webkit.WebViewClient#onTooManyRedirects(android.webkit.WebView, android.os.Message, android.os.Message)
*/
@Override
public void onTooManyRedirects(WebView view,
Message cancelMsg, Message continueMsg) {
// TODO Auto-generated method stub
super.onTooManyRedirects(view, cancelMsg, continueMsg);
}
/* (non-Javadoc)
* @see android.webkit.WebViewClient#onUnhandledKeyEvent(android.webkit.WebView, android.view.KeyEvent)
*/
@Override
public void onUnhandledKeyEvent(WebView view,
KeyEvent event) {
// TODO Auto-generated method stub
super.onUnhandledKeyEvent(view, event);
}
/* (non-Javadoc)
* @see android.webkit.WebViewClient#shouldInterceptRequest(android.webkit.WebView, java.lang.String)
*/
@Override
public WebResourceResponse shouldInterceptRequest(
WebView view, String url) {
// TODO Auto-generated method stub
return super.shouldInterceptRequest(view, url);
}
/* (non-Javadoc)
* @see android.webkit.WebViewClient#shouldOverrideKeyEvent(android.webkit.WebView, android.view.KeyEvent)
*/
@Override
public boolean shouldOverrideKeyEvent(WebView view,
KeyEvent event) {
// TODO Auto-generated method stub
return super.shouldOverrideKeyEvent(view, event);
}
/* (non-Javadoc)
* @see android.webkit.WebViewClient#shouldOverrideUrlLoading(android.webkit.WebView, java.lang.String)
*/
@Override
public boolean shouldOverrideUrlLoading(WebView view,
String url) {
// TODO Auto-generated method stub
return super.shouldOverrideUrlLoading(view, url);
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(webView, url);
// print("webView onPageFinished: " + url);
if (url != null) {
hookWebElements(view, javaScript);
}
}
});
}
}
});
}
public void hookWebView(final WebView webView) {
setHookedWebChromeClient(webView);
final String javaScript = getJavaScriptAsString();
hookWebElements(webView, javaScript);
setHookedWebViewClient(webView, javaScript);
}
/**
* @param webView
* @param javaScript
*/
public void hookWebElements(final WebView webView, final String javaScript) {
webView.post(new Runnable() {
public void run() {
if (webView != null) {
webView.loadUrl("javascript:" + javaScript);
}
}
});
// viewRecorder.getLocalLib().getCurrentActivity().setProgress(10000);
}
private String getJavaScriptAsString() {
StringBuffer javaScript = new StringBuffer();
try {
// InputStream fis =
// getClass().getResourceAsStream("WebElementRecorder.js");
InputStream fis = new FileInputStream(CafeTestCase.mTargetFilesDir + "/WebElementRecorder.js");
BufferedReader input = new BufferedReader(new InputStreamReader(fis));
String line = null;
while ((line = input.readLine()) != null) {
javaScript.append(line);
javaScript.append("\n");
}
input.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
return javaScript.toString();
}
private void print(String message) {
if (ViewRecorder.DEBUG) {
Log.i("ViewRecorder", message);
} else {
Log.i("ViewRecorder", DESEncryption.encryptStr(message));
}
}
class WebElementEventCreator {
private boolean isFinished = false;
private ViewRecorder viewRecorder = null;
public final static boolean DEBUG = true;
public WebElementEventCreator(ViewRecorder viewRecorder) {
this.viewRecorder = viewRecorder;
}
public void prepareForStart() {
setFinished(false);
}
public void createWebElementEvent(String information, WebView webView) {
String action = "";
long time = 0;
String familyString = "";
int x = 0;
int y = 0;
int width = 0;
int height = 0;
String value = "";
String tag = "";
try {
JSONObject jsonObject = new JSONObject(information);
action = jsonObject.getString("action");
time = Long.valueOf(jsonObject.getString("time"));
familyString = jsonObject.getString("familyString");
x = Math.round(Float.valueOf(jsonObject.getString("left")));
y = Math.round(Float.valueOf(jsonObject.getString("top")));
width = Math.round(Float.valueOf(jsonObject.getString("width")));
height = Math.round(Float.valueOf(jsonObject.getString("height")));
value = jsonObject.isNull("value") ? "" : jsonObject.getString("value");
tag = jsonObject.isNull("tag") ? "" : jsonObject.getString("tag");
} catch (Exception ignored) {
if (DEBUG) {
ignored.printStackTrace();
}
}
float scale = webView.getScale();
int[] locationOfWebViewXY = new int[2];
webView.getLocationOnScreen(locationOfWebViewXY);
int locationX = (int) (locationOfWebViewXY[0] + (x + (Math.floor(width / 2))) * scale);
int locationY = (int) (locationOfWebViewXY[1] + (y + (Math.floor(height / 2))) * scale);
if (DEBUG) {
System.out.println("[action:" + action + "] [familyString:" + familyString + "] [locationX:"
+ locationX + "] [locationY:" + locationY + "] [time:" + time + "] [tag:" + tag + "]");
}
WebElementRecordEvent event = new WebElementRecordEvent(webView, familyString, action, locationX,
locationY, time, value, tag);
offerWebElementRecordEventQueue(event);
}
public void setFinished(boolean isFinished) {
this.isFinished = isFinished;
}
public boolean isFinished() {
return isFinished;
}
private boolean waitForWebElementsToBeCreated() {
final long endTime = SystemClock.uptimeMillis() + 20;
while (SystemClock.uptimeMillis() < endTime) {
if (isFinished) {
return true;
}
sleep(2);
}
return false;
}
}
class WebElementRecordEvent {
public View view;
public String familyString;
public String action;
public int x;
public int y;
public long time;
public String value;
public String tag;
public WebElementRecordEvent(View view, String familyString, String action, int x, int y, long time,
String value, String tag) {
this.view = view;
this.familyString = familyString;
this.action = action;
this.x = x;
this.y = y;
this.time = time;
this.value = value;
this.tag = tag;
}
}
class WebElementClickEvent extends OutputEvent {
public WebElementClickEvent(View view) {
this.view = view;
this.priority = PRIORITY_WEBELEMENT_CLICK;
}
}
class WebElementChangeEvent extends OutputEvent {
public WebElementChangeEvent(View view) {
this.view = view;
this.priority = PRIORITY_WEBELEMENT_CHANGE;
}
}
}